# Usage :
#
# Variable : ENABLE_LTO | Enable or disable LTO support for this build
#
# find_lto(lang) - lang is C or CXX (the language to test LTO for) -
# call it after project() so that the compiler is already detected
#
# This will check for LTO support and create a
# target_enable_lto(target [debug,optimized,general]) macro. The 2nd
# parameter has the same meaning as in target_link_libraries, and is
# used to enable LTO only for those build configurations 'debug' is by
# default the Debug configuration, and 'optimized' all the other
# configurations
#
# if ENABLE_LTO is set to false, an empty macro will be generated
#
# Then to enable LTO for your target use
#
# target_enable_lto(mytarget general)
#
# It is however recommended to use it only for non debug builds the
# following way :
#
# target_enable_lto(mytarget optimized)
#
# Note : For CMake versions < 3.9, target_link_library is used in it's
# non plain version. You will need to specify PUBLIC/PRIVATE/INTERFACE
# to all your other target_link_library calls for the target
#
# WARNING for cmake versions older than 3.9 : This module will
# override CMAKE_AR CMAKE_RANLIB and CMAKE_NM by the gcc versions if
# found when building with gcc

# License:
#
# Copyright (C) 2016 Lectem <lectem@gmail.com>
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the 'Software') deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject
# to the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

macro(find_lto lang)
  if (ENABLE_LTO AND NOT LTO_${lang}_CHECKED)

    # LTO support was added for clang/gcc in 3.9
    if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.9)
      cmake_policy(SET CMP0054 NEW)
      message(STATUS "Checking for LTO Compatibility")
      # Since GCC 4.9 we need to use gcc-ar / gcc-ranlib / gcc-nm
      if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID
          MATCHES "Clang"
          )
        if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU"
            AND NOT CMAKE_GCC_AR
            OR NOT CMAKE_GCC_RANLIB
            OR NOT CMAKE_GCC_NM
            )
          find_program(
              CMAKE_GCC_AR
              NAMES "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar"
              "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ar-${_version}"
              DOC "gcc provided wrapper for ar which adds the --plugin option"
          )
          find_program(
              CMAKE_GCC_RANLIB
              NAMES "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib"
              "${_CMAKE_TOOLCHAIN_PREFIX}gcc-ranlib-${_version}"
              DOC "gcc provided wrapper for ranlib which adds the --plugin option"
          )
          # Not needed, but at least stay coherent
          find_program(
              CMAKE_GCC_NM
              NAMES "${_CMAKE_TOOLCHAIN_PREFIX}gcc-nm"
              "${_CMAKE_TOOLCHAIN_PREFIX}gcc-nm-${_version}"
              DOC "gcc provided wrapper for nm which adds the --plugin option"
          )
          mark_as_advanced(CMAKE_GCC_AR CMAKE_GCC_RANLIB CMAKE_GCC_NM)
          set(CMAKE_LTO_AR ${CMAKE_GCC_AR})
          set(CMAKE_LTO_RANLIB ${CMAKE_GCC_RANLIB})
          set(CMAKE_LTO_NM ${CMAKE_GCC_NM})
        endif ()
        if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
          set(CMAKE_LTO_AR ${CMAKE_AR})
          set(CMAKE_LTO_RANLIB ${CMAKE_RANLIB})
          set(CMAKE_LTO_NM ${CMAKE_NM})
        endif ()

        if (CMAKE_LTO_AR AND CMAKE_LTO_RANLIB)
          set(__lto_flags -flto)

          if (NOT CMAKE_${lang}_COMPILER_VERSION VERSION_LESS 4.7)
            list(APPEND __lto_flags -fno-fat-lto-objects)
          endif ()

          if (NOT DEFINED CMAKE_${lang}_PASSED_LTO_TEST)
            set(__output_dir
                "${CMAKE_PLATFORM_INFO_DIR}/LtoTest1${lang}"
                )
            file(MAKE_DIRECTORY "${__output_dir}")
            set(__output_base "${__output_dir}/lto-test-${lang}")

            execute_process(
                COMMAND ${CMAKE_COMMAND} -E echo "void foo() {}"
                COMMAND ${CMAKE_${lang}_COMPILER} ${__lto_flags} -c -xc
                - -o "${__output_base}.o"
                RESULT_VARIABLE __result
                ERROR_QUIET OUTPUT_QUIET
            )

            if ("${__result}" STREQUAL "0")
              execute_process(
                  COMMAND ${CMAKE_LTO_AR} cr "${__output_base}.a"
                  "${__output_base}.o"
                  RESULT_VARIABLE __result
                  ERROR_QUIET OUTPUT_QUIET
              )
            endif ()

            if ("${__result}" STREQUAL "0")
              execute_process(
                  COMMAND ${CMAKE_LTO_RANLIB} "${__output_base}.a"
                  RESULT_VARIABLE __result
                  ERROR_QUIET OUTPUT_QUIET
              )
            endif ()

            if ("${__result}" STREQUAL "0")
              execute_process(
                  COMMAND ${CMAKE_COMMAND} -E echo
                  "void foo(); int main() {foo();}"
                  COMMAND
                  ${CMAKE_${lang}_COMPILER} ${__lto_flags} -xc - -x
                  none "${__output_base}.a" -o "${__output_base}"
                  RESULT_VARIABLE __result
                  ERROR_QUIET OUTPUT_QUIET
              )
            endif ()

            if ("${__result}" STREQUAL "0")
              set(__lto_found TRUE)
            endif ()

            set(CMAKE_${lang}_PASSED_LTO_TEST
                ${__lto_found}
                CACHE
                INTERNAL
                "If the compiler passed a simple LTO test compile"
                )
          endif ()
          if (CMAKE_${lang}_PASSED_LTO_TEST)
            message(STATUS "Checking for LTO Compatibility - works")
            set(LTO_${lang}_SUPPORT
                TRUE
                CACHE BOOL "Do we have LTO support ?"
                )
            set(LTO_COMPILE_FLAGS
                -flto
                CACHE STRING "Link Time Optimization compile flags"
                )
            set(LTO_LINK_FLAGS
                -flto
                CACHE STRING "Link Time Optimization link flags"
                )
          else ()
            message(
                STATUS "Checking for LTO Compatibility - not working"
            )
          endif ()

        endif ()
      elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
        message(
            STATUS
            "Checking for LTO Compatibility - works (assumed for clang)"
        )
        set(LTO_${lang}_SUPPORT
            TRUE
            CACHE BOOL "Do we have LTO support ?"
            )
        set(LTO_COMPILE_FLAGS
            -flto
            CACHE STRING "Link Time Optimization compile flags"
            )
        set(LTO_LINK_FLAGS
            -flto
            CACHE STRING "Link Time Optimization link flags"
            )
      elseif (CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
        message(STATUS "Checking for LTO Compatibility - works")
        set(LTO_${lang}_SUPPORT
            TRUE
            CACHE BOOL "Do we have LTO support ?"
            )
        set(LTO_COMPILE_FLAGS
            /GL
            CACHE STRING "Link Time Optimization compile flags"
            )
        set(LTO_LINK_FLAGS
            -LTCG:INCREMENTAL
            CACHE STRING "Link Time Optimization link flags"
            )
      else ()
        message(
            STATUS
            "Checking for LTO Compatibility - compiler not handled by module"
        )
      endif ()
      mark_as_advanced(
          LTO_${lang}_SUPPORT LTO_COMPILE_FLAGS LTO_LINK_FLAGS
      )

      set(LTO_${lang}_CHECKED
          TRUE
          CACHE INTERNAL ""
          )

      if (CMAKE_GCC_AR
          AND CMAKE_GCC_RANLIB
          AND CMAKE_GCC_NM
          )
        # THIS IS HACKY BUT THERE IS NO OTHER SOLUTION ATM
        set(CMAKE_AR
            ${CMAKE_GCC_AR}
            CACHE FILEPATH "Forcing gcc-ar instead of ar" FORCE
            )
        set(CMAKE_NM
            ${CMAKE_GCC_NM}
            CACHE FILEPATH "Forcing gcc-nm instead of nm" FORCE
            )
        set(CMAKE_RANLIB
            ${CMAKE_GCC_RANLIB}
            CACHE FILEPATH "Forcing gcc-ranlib instead of ranlib"
            FORCE
            )
      endif ()
    endif (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS
        3.9
        )
  endif (ENABLE_LTO AND NOT LTO_${lang}_CHECKED)

  if (ENABLE_LTO)
    # Special case for cmake older than 3.9, using a library for
    # gcc/clang, but could setup the flags directly. Taking advantage
    # of the [debug,optimized] parameter of target_link_libraries
    if (${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION} VERSION_LESS 3.9)
      if (LTO_${lang}_SUPPORT)
        if (NOT TARGET __enable_lto_tgt)
          add_library(__enable_lto_tgt INTERFACE)
        endif ()
        target_compile_options(
            __enable_lto_tgt INTERFACE ${LTO_COMPILE_FLAGS}
        )
        # this might not work for all platforms... in which case we'll
        # have to set the link flags on the target directly
        target_link_libraries(
            __enable_lto_tgt INTERFACE ${LTO_LINK_FLAGS}
        )
        macro(target_enable_lto _target _build_configuration)
          if (${_build_configuration} STREQUAL "optimized"
              OR ${_build_configuration} STREQUAL "debug"
              )
            target_link_libraries(
                ${_target} PRIVATE ${_build_configuration}
                __enable_lto_tgt
            )
          else ()
            target_link_libraries(${_target} PRIVATE __enable_lto_tgt)
          endif ()
        endmacro()
      else ()
        # In old cmake versions, we can set
        # INTERPROCEDURAL_OPTIMIZATION even if not supported by the
        # compiler So if we didn't detect it, let cmake give it a try
        set(__IPO_SUPPORTED TRUE)
      endif ()
    else ()
      cmake_policy(SET CMP0069 NEW)
      include(CheckIPOSupported)
      # Optional IPO. Do not use IPO if it's not supported by
      # compiler.
      check_ipo_supported(RESULT __IPO_SUPPORTED OUTPUT output)
      if (NOT __IPO_SUPPORTED)
        message(STATUS "IPO is not supported or broken.")
      else ()
        message(STATUS "IPO is supported")
      endif ()
    endif ()
    if (__IPO_SUPPORTED)
      macro(target_enable_lto _target _build_configuration)
        if (NOT ${_build_configuration} STREQUAL "debug")
          # enable for all configurations
          set_target_properties(
              ${_target} PROPERTIES INTERPROCEDURAL_OPTIMIZATION TRUE
          )
        endif ()
        if (${_build_configuration} STREQUAL "optimized")
          # blacklist debug configurations
          set(__enable_debug_lto FALSE)
        else ()
          # enable only for debug configurations
          set(__enable_debug_lto TRUE)
        endif ()
        get_property(
            DEBUG_CONFIGURATIONS GLOBAL PROPERTY DEBUG_CONFIGURATIONS
        )
        if (NOT DEBUG_CONFIGURATIONS)
          set(DEBUG_CONFIGURATIONS DEBUG) # This is what is done by
          # CMAKE internally... since
          # DEBUG_CONFIGURATIONS is
          # empty by default
        endif ()
        foreach (config IN LISTS DEBUG_CONFIGURATIONS)
          set_target_properties(
              ${_target}
              PROPERTIES INTERPROCEDURAL_OPTIMIZATION_${config}
              ${__enable_debug_lto}
          )
        endforeach ()
      endmacro()
    endif ()
  endif ()
  if (NOT COMMAND target_enable_lto)
    macro(target_enable_lto _target _build_configuration)

    endmacro()
  endif ()
endmacro()